#pragma once

#include <iostream>
#include <windows.h>
#include <gl\gl.h>
#include <gl\glu.h>
#include <Utilities\glext.h>

class FramebufferObject
{
public:
	//Ctor/Dtor
	FramebufferObject(void);
	virtual ~FramebufferObject(void);

	//Bind this FBO as current render target
	void Bind(void);
	//Bind a texture to the "attachment" point of this FBO
	void AttachTexture(const GLenum attachment, const GLenum texType, const GLuint texId)
	{
		AttachTexture(attachment, texType, texId, 0);
	}

	void AttachTexture(const GLenum attachment, const GLenum texType, const GLuint texId, const int mipLevel)
	{
		AttachTexture(attachment, texType, texId, mipLevel, 0);
	}

	void AttachTexture(const GLenum attachment, const GLenum texType, const GLuint texId, const int mipLevel, const int layer)
	{
		GuardedBind();

#ifndef NDEBUG
		if(GetAttachedId(attachment) != texId)
		{
#endif
			FramebufferTextureND(attachment, texType, texId, mipLevel, layer);
#ifndef NDEBUG
		}
		else
		{
			std::cerr << "Framebuffer Object::AttachTexture PERFORMANCE WARNING:" << std::endl << "\tRedundant bind of texture (id = " << texId << ")." << endl;
		}
#endif

		GuardedUnbind();
	}

	//Bind a render buffer to the "attachment" point of this FBO
	void AttachRenderBuffer(const GLenum attachment, const GLuint buffId);
	//Free any resource bound to the "attachment" point of this FBO
	void Unattach(const GLenum attachment)
	{
		GuardedBind();
		GLenum type = GetAttachedType(attachment);

		switch(type)
		{
		case GL_NONE:
			break;
		case GL_RENDERBUFFER_EXT:
			AttachRenderBuffer(attachment, 0);
			break;
		case GL_TEXTURE:
			AttachTexture(attachment, GL_TEXTURE_2D, 0);
			break;
		default:
			std::cerr << "FramebufferObject::unbind_attachment ERROR: Unknown attached resource type" << std::endl;
		}
		GuardedUnbind();
	}

	//Is this FBO currently a valid render target?
	bool GetIsValid(void);
	bool GetIsValid(std::ostream& ostr);

	//Is attached type GL_RENDERBUFFER_EXT or GL_TEXTURE?
	const GLenum GetAttachedType(const GLenum attachment);
	//What is the Id of RenderbufferObject/texture currently attached to "attachement?"
	const GLuint GetAttachedId(const GLenum attachment);
	//Which mip level is currently attached to "attachement?"
	const GLint GetAttachedMipLevel(const GLenum attachment);
	//Which cube face is currently attached to "attachment?"
	const GLint GetAttachedCubeFace(const GLenum attachment);
	//Which z-slice is currently attached to "attachment?"
	const GLint GetAttachedZSlice(const GLenum attachment);

	//Return number of color attachments permitted
	static int GetMaxColorAttachments(void);
	//Disable all FBO rendering and return to traditional, windowing-system controlled framebuffer
	static void Disable(void);

private:
	FramebufferObject(const FramebufferObject&);

	GLuint _FBOObject;
	GLuint _savedFBOObject;

	void GuardedBind(void);
	void GuardedUnbind(void) const;
	void FramebufferTextureND(const GLenum attachment, const GLenum texType, const GLuint texId, const int mipLevel, const int layer);

	static GLuint GenerateFBOId(void);
};
